'************************************************************************************
' Copyright © 2003-2005 Kathleen Dollard
'
' This software is provided 'as-is', without any express or implied warranty. In no 
' event will the author be held liable for any damages arising from the use of this
' software.
' 
' You may use this software for any purpose, including commercial applications, 
' and you may redistribute this software as part of application delivery: however, 
' this copyright notice may not be removed and you may not otherwise redistribute 
' this software directly other than providing a link to wwww.gendotnet.com. 
'
' You may alter this software to better server your needs (I'd like to hear about your 
' changes in order to make the software better). If you alter the softwre, you must  
' clearly mark it as altered and must not be misrepresented as the original software.
' nor wholly your original work.
'*************************************************************************************
Option Strict On
Option Explicit On 

Imports System
Imports System.Collections.Generic

Namespace KADGen.CodeGenerationSupport
   Public Class UpdateProject

      Private Const msbuildNamespace As String = "http://schemas.microsoft.com/developer/msbuild/2003"

#Region "Public properties and methods"
      Public Sub Run( _
                     ByVal node As Xml.XmlNode, _
                     ByVal runUtility As RunUtility, _
                     ByVal statusDirective As IStatusDirective)
         Dim statusoutput As IStatusOutput
         Dim updateProject As CodeGenerationSupport.IUpdateProjectDirective
         Dim projectFileName As String
         Dim directory As String
         Dim projectType As TargetLanguage
         Dim xmlScript As New Xml.XmlDocument
         updateProject = CType(node, CodeGenerationSupport.IUpdateProjectDirective)
         projectFileName = updateProject.ProjectFileName
         directory = updateProject.FileDirectory
         projectType = updateProject.ProjectType
         statusoutput = statusDirective.AddStatusOutput( _
                  directory, _
                  FileTreatment.Unknown)
         Try
            xmlScript.Load(projectFileName)
            Dim nsMgr As New Xml.XmlNamespaceManager(xmlScript.NameTable)
            nsMgr.AddNamespace("ms", msbuildNamespace)
            Dim groupNodes As Xml.XmlNodeList = xmlScript.SelectNodes("//ms:ItemGroup", nsMgr)
            ' TODO: If group nodes is empty, should add one
            Dim hasChanges As Boolean
            Dim compileNodes As Xml.XmlNodeList = xmlScript.SelectNodes("//ms:ItemGroup/ms:Compile", nsMgr)
            ' TODO: This should probably be an option
            If RemoveMissingFiles(compileNodes, projectFileName, directory, projectType, nsMgr) Then
               hasChanges = True
            End If
            If AddNewFiles(groupNodes(0), projectFileName, directory, projectType, nsMgr) Then
               hasChanges = True
            End If
            '' The compiles are supposed to all be together and that's all that is supported in this version
            'For Each groupNode As Xml.XmlNode In groupNodes
            '   If groupNode.ChildNodes.Count > 0 AndAlso groupNode.ChildNodes(0).LocalName = "Compile" Then
            '      If UpdateCompileNodes(groupNode, projectFileName, directory, projectType, nsMgr) Then
            '         hasChanges = True
            '      End If
            '   End If
            'Next
            If hasChanges Then
               xmlScript.Save(projectFileName)
            End If
         Catch ex As System.Exception
            statusoutput.AddStatusException("CreateMetadata.Run", "Failure creating file", ex)
         End Try
         statusoutput.Status = statusoutput.ChildStatus
      End Sub
#End Region

#Region "Private properties and methods"
      Private Function RemoveMissingFiles( _
               ByVal compileNodes As Xml.XmlNodeList, _
               ByVal projectFileName As String, _
               ByVal directory As String, _
               ByVal projectType As TargetLanguage, _
               ByVal nsmgr As Xml.XmlNamespaceManager) _
               As Boolean
         Dim fullProjectFileName As String = IO.Path.GetFullPath(projectFileName)
         Dim projectDirectory As String = IO.Path.GetDirectoryName(fullProjectFileName)
         Dim fullDirectory As String = IO.Path.GetFullPath(directory)
         Dim hasChanges As Boolean = False
         ' Check for any files that no longer exist
         For Each compileNode As Xml.XmlNode In compileNodes
            ' This relies on standard naming ocnventions for dependent things
            Dim fileName As String = Utility.Tools.GetAttributeOrEmpty(compileNode, "Include")
            Dim fullFileName As String = IO.Path.Combine(projectDirectory, fileName)
            ' resolve any relative paths
            fullFileName = IO.Path.GetFullPath(fullFileName)
            If fullFileName.StartsWith(projectDirectory & "/") Or _
                     fullFileName.StartsWith(projectDirectory & "\") Then
               ' This is a file we need to check
               If Not IO.File.Exists(fullFileName) Then
                  hasChanges = True
                  compileNode.ParentNode.RemoveChild(compileNode)
               End If
            End If
         Next
         Return hasChanges
      End Function

      Private Function AddNewFiles( _
               ByVal groupNode As Xml.XmlNode, _
               ByVal projectFileName As String, _
               ByVal directory As String, _
               ByVal projectType As TargetLanguage, _
               ByVal nsmgr As Xml.XmlNamespaceManager) _
               As Boolean
         Dim directoryInfo As New IO.DirectoryInfo(directory)
         Dim extension As String
         Dim fullProjectFileName As String = IO.Path.GetFullPath(projectFileName)
         Dim projectDirectory As String = IO.Path.GetDirectoryName(fullProjectFileName)
         Dim hasChanges As Boolean = False
         If projectType = TargetLanguage.CSharp Then
            extension = ".cs"
         Else
            extension = ".vb"
         End If
         If AddNewCompileNodes(groupNode, extension, projectDirectory, directoryInfo, nsmgr) Then
            hasChanges = True
         End If
         If CheckDependencies(projectDirectory, groupNode, nsmgr) Then
            hasChanges = True
         End If
         Return hasChanges
      End Function

      'Private Function UpdateCompileNodes(ByVal groupNode As Xml.XmlNode, _
      '         ByVal projectFileName As String, _
      '         ByVal directory As String, _
      '         ByVal projectType As TargetLanguage, _
      '         ByVal nsmgr As Xml.XmlNamespaceManager) _
      '         As Boolean
      '   Dim fullProjectFileName As String = IO.Path.GetFullPath(projectFileName)
      '   Dim projectDirectory As String = IO.Path.GetDirectoryName(fullProjectFileName)
      '   Dim fullDirectory As String = IO.Path.GetFullPath(directory)
      '   Dim extension As String
      '   Dim hasChanges As Boolean = False
      '   ' Check for any files that no longer exist
      '   For Each compileNode As Xml.XmlNode In groupNode.ChildNodes
      '      ' This relies on standard naming ocnventions for dependent things
      '      Dim fileName As String = Utility.Tools.GetAttributeOrEmpty(compileNode, "Include")
      '      Dim fullFileName As String = IO.Path.Combine(projectDirectory, fileName)
      '      ' resolve any relative paths
      '      fullFileName = IO.Path.GetFullPath(fullFileName)
      '      If fullFileName.StartsWith(projectDirectory & "/") Then
      '         ' This is a file we need to check
      '         If Not IO.File.Exists(fullFileName) Then
      '            hasChanges = True
      '            groupNode.RemoveChild(compileNode)
      '         End If
      '      End If
      '   Next
      '   ' Add New files
      '   If projectType = TargetLanguage.CSharp Then
      '      extension = ".cs"
      '   Else
      '      extension = ".vb"
      '   End If
      '   If AddNewCompileNodes(groupNode, extension, projectDirectory, directoryInfo) Then
      '      hasChanges = True
      '   End If
      '   If CheckDependencies(projectDirectory, groupNode, nsmgr) Then
      '      hasChanges = True
      '   End If
      '   Return hasChanges

      'End Function

      Private Function AddNewCompileNodes( _
               ByVal groupNode As Xml.XmlNode, _
               ByVal extension As String, _
               ByVal projectDirectory As String, _
               ByVal directoryInfo As IO.DirectoryInfo, _
               ByVal nsmgr As Xml.XmlNamespaceManager) _
               As Boolean
         ' For now, demand .vb and .cs extensions
         Dim hasChanges As Boolean = False
         For Each file As IO.FileInfo In directoryInfo.GetFiles("*" & extension)
            ' Cut down to the obvious ones - some variation in structure would be legal
            Dim testNodes As Xml.XmlNodeList = groupNode.SelectNodes("//ms:ItemGroup/ms:Compile[contains(@Include, '" & file.Name & "')]", nsmgr)
            Dim fullFileName As String = file.FullName
            Dim found As Boolean = False
            For Each testNode As Xml.XmlNode In testNodes
               Dim fullTestName As String = Utility.Tools.GetAttributeOrEmpty(testNode, "Include")
               fullTestName = IO.Path.Combine(projectDirectory, fullTestName)
               ' clean up any relative pathing
               fullTestName = IO.Path.GetFullPath(fullTestName)
               If fullTestName = fullFileName Then
                  found = True
                  Exit For
               End If
            Next
            If Not found Then
               hasChanges = True
               Dim newNode As Xml.XmlNode = groupNode.OwnerDocument.CreateElement("Compile", msbuildNamespace)
               Dim attribute As Xml.XmlAttribute = groupNode.OwnerDocument.CreateAttribute("Include")
               newNode.Attributes.Append(attribute)
               Dim relativePath As String = fullFileName
               If relativePath.StartsWith(projectDirectory & "\") Then
                  relativePath = relativePath.Replace(projectDirectory & "\", "")
               End If
               attribute.Value = relativePath
               ' I'm skipping subtypes for now because of problems with parsing VB and C# files
               Dim subTypeNode As Xml.XmlNode = groupNode.OwnerDocument.CreateElement("SubType", msbuildNamespace)
               subTypeNode.AppendChild(groupNode.OwnerDocument.CreateTextNode("Code"))
               newNode.AppendChild(subTypeNode)
               groupNode.AppendChild(newNode)
            End If
         Next
         For Each childInfo As IO.DirectoryInfo In directoryInfo.GetDirectories
            If AddNewCompileNodes(groupNode, extension, projectDirectory, childInfo, nsmgr) Then
               hasChanges = True
            End If
         Next
         Return hasChanges
      End Function

      Private Function CheckDependencies( _
                  ByVal projectDirectory As String, _
                  ByVal groupNode As Xml.XmlNode, _
                  ByVal nsmgr As Xml.XmlNamespaceManager) _
                  As Boolean
         Dim hasChanges As Boolean
         ' For now, I'm insisting they be in the same directory
         For Each compileNode As Xml.XmlNode In groupNode.ChildNodes
            Dim include As String = Utility.Tools.GetAttributeOrEmpty(compileNode, "Include")
            If include.EndsWith(".Designer.vb") Or include.EndsWith(".Designer.cs") Then
               Dim dependentNode As Xml.XmlNode = compileNode.SelectSingleNode("ms:DependentUpon", nsmgr)
               If dependentNode Is Nothing Then
                  Dim fullIncludeName As String = IO.Path.Combine(projectDirectory, include)
                  Dim fullIncludeTestName As String = fullIncludeName.Replace("Designer.", "")
                  fullIncludeName = IO.Path.GetFullPath(fullIncludeName)
                  Dim shortInclude As String = IO.Path.GetFileName(include)
                  Dim dependentName As String = shortInclude.Replace("Designer.", "")
                  Dim testNodes As Xml.XmlNodeList = groupNode.SelectNodes("*[contains(@Include, '" & dependentName & "')]")
                  Dim found As Boolean = False
                  For Each testNode As Xml.XmlNode In testNodes
                     Dim testName As String = Utility.Tools.GetAttributeOrEmpty(testNode, "Include")
                     testName = IO.Path.Combine(projectDirectory, testName)
                     testName = IO.Path.GetFullPath(testName)
                     If fullIncludeTestName = testName Then
                        found = True
                        Exit For
                     End If
                  Next
                  If found Then
                     ' Create dependent node
                     hasChanges = True
                     Dim newNode As Xml.XmlNode = groupNode.OwnerDocument.CreateElement("DependentUpon", msbuildNamespace)
                     newNode.InnerText = IO.Path.GetFileName(dependentName)
                     compileNode.AppendChild(newNode)
                  End If
               End If
            End If
         Next
         Return hasChanges
      End Function
#End Region
   End Class
End Namespace